package jp.lab1;
/******************************************************************
*
*	CyberUPnP for Java
*
*	Copyright (C) Satoshi Konno 2002
*
*	File : ClockDevice.java
*
******************************************************************/



import java.awt.Component;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.DatagramPacket;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Random;
import java.util.Vector;


import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;


import org.cybergarage.upnp.Action;
import org.cybergarage.upnp.Argument;
import org.cybergarage.upnp.Device;
import org.cybergarage.upnp.Service;
import org.cybergarage.upnp.ServiceList;
import org.cybergarage.upnp.StateVariable;
import org.cybergarage.upnp.UPnP;

import org.cybergarage.upnp.ssdp.SSDPPacket;

import org.cybergarage.upnp.control.ActionListener;
import org.cybergarage.upnp.control.QueryListener;
import org.cybergarage.upnp.device.InvalidDescriptionException;
import org.cybergarage.util.Debug;

import org.geed.testbed.sub1.FooMUSocket;
import org.geed.testbed.sub1.FooPacket;
import org.geed.testbed.sub1.FooRekeyListener;
import org.geed.testbed.sub1.FooRekeyServer;
import org.geed.testbed.sub1.FooUnicastServer;
import org.geed.testbed.sub1.FooRequest;
import org.geed.testbed.sub1.FooResponse;
import org.geed.testbed.sub1.FooUnicastListener;
import org.geed.testbed.sub1.FooUSocket;


public class AirconDevice extends Device implements ActionListener, QueryListener
			, FooUnicastListener, FooRekeyListener, Runnable
{
	private final static String DESCRIPTION_FILE_NAME = "description/description.xml";

	private StateVariable tempVar;
	private StateVariable secureGroupVar;
	private StateVariable keyInformation;
	
	//Secure group communication variable
	private int keylen=16;
	private int l=128; //ramda
	private byte[][] publicIDBytes;
	private byte[][] secretBytes;
	
	private long nodeConfirmTimeout = 10000; //10 seconds
	//private byte[] secret;
	private FooUnicastServer unicastServer;
	private FooRekeyServer rekeyServer;
	
	//current group key
	private byte[] currentGroupkey;
	private String currentGroupkeyID;
	private int groupkeyID = 0;
	private String name = null;
	public SSDPPacket ssdpPacket = null;
	

	private SecureNodeList secureNodeList = null;
	
	private FooRequest lastRekey;
	private Vector<SecureNode> confirmList = new Vector<SecureNode>();
//	private long nodeSizeStart =0;
//	private long nodeSizeLast = 0;
	
	
	
	public void run(){
		//System.out.println("SEARCH RESPONSE START IN NEW THREAD");
		super.deviceSearchResponse(this.ssdpPacket);
	}
	
	public void deviceSearchReceived(SSDPPacket ssdpPacket)
	{
		this.ssdpPacket = ssdpPacket;
		Thread t = new Thread(this);
		
		t.start();
		
	}
	
	public static void main(String[] args){
		AirconDevice dev = null;
		try{ 
			dev = new AirconDevice("0");//id = 0
			dev.start();
		}catch(Exception e){}
		

		
	}
	
	public AirconDevice(String name) throws InvalidDescriptionException
	{
		super(new File(DESCRIPTION_FILE_NAME));

		Action getPowerAction = getAction("GetPower");
		getPowerAction.setActionListener(this);
		
		Action setPowerAction = getAction("SetPower");
		setPowerAction.setActionListener(this);
		
		Action getTempAction = getAction("GetTemp");
		getTempAction.setActionListener(this);
		
		Action setTempAction = getAction("SetTemp");
		setTempAction.setActionListener(this);
		
		//Action isSupportSecureGroup = getAction("IsSupportSecureGroup");
		//isSupportSecureGroup.setActionListener(this);
		
		Action isSecured = getAction("IsSecured");
		isSecured.setActionListener(this);
		
		Action getGroupKey = getAction("GetGroupKey");
		getGroupKey.setActionListener(this);
		
		Action getKeyInformation = getAction("GetKeyInformation");
		getKeyInformation.setActionListener(this);
		
		Action getAlgorithmAndProtocols = getAction("GetAlgorithmAndProtocols");
		getAlgorithmAndProtocols.setActionListener(this);
		
		Action getLifetimeSequenceBase = getAction("GetLifetimeSequenceBase");
		getLifetimeSequenceBase.setActionListener(this);
		
		Action leaveGroup = getAction("LeaveGroup");
		leaveGroup.setActionListener(this);
		
		Action decryptAndExecute = getAction("DecryptAndExecute");
		decryptAndExecute.setActionListener(this);
		
		Action authenticate = getAction("Authenticate");
		authenticate.setActionListener(this);
		
		ServiceList serviceList = getServiceList();
		Service service = serviceList.getService(0);
		service.setQueryListener(this);

		tempVar = getStateVariable("Temp");
		keyInformation = getStateVariable("KeyInformation");
		keyInformation.setValue("this is key information xxx");
		
		
		setTempture("18");
		
		//Use only ipv4 address
		UPnP.setEnable(UPnP.USE_ONLY_IPV4_ADDR);
		//Debug.on();
		this.secureNodeList = new SecureNodeList();
		
		this.name = name;
		readKeyFile();
	}
	
	public String getName(){
		return this.name;
	}
	
	public long getNodeSize(){
		return 0;
	}
	
	
	public boolean start(){
		unicastServer = new FooUnicastServer();
		unicastServer.setPort(4555);
		unicastServer.setUnicastListener(this);
		unicastServer.start();
		
		rekeyServer = new FooRekeyServer();
		rekeyServer.setPort(4556);
		rekeyServer.setRekeyListener(this);
		rekeyServer.start();
		
		
		Debug.off();
		
		//System.out.println("Secure AirCon device started");
		//getMemoryUse();
		return super.start();
	}

	////////////////////////////////////////////////
	//	Component
	////////////////////////////////////////////////

	private Component comp;
	
	public void setComponent(Component comp)
	{
		this.comp = comp;	
	}
	
	public Component getComponent()
	{
		return comp;
	}
	
	////////////////////////////////////////////////
	//	on/off
	////////////////////////////////////////////////

	private boolean onFlag = true;
	
	public void on()
	{
		onFlag = true;
	}

	public void off()
	{
		onFlag = false;
	}

	public boolean isOn()
	{
		return onFlag;
	}
	
	public void setPowerState(String state)
	{
		if (state == null) {
			off();
			return;
		}
		if (state.compareTo("1") == 0) {
			on();
			return;
		}
		if (state.compareTo("0") == 0) {
			off();
			return;
		}
	}
	
	public String getPowerState()
	{
		if (onFlag == true)
			return "1";
		return "0";
	}

	////////////////////////////////////////////////
	//	on/off
	////////////////////////////////////////////////

	private int tempture = 18;
	
	public void setTempture(String state)
	{
		if (isOn() == false)
			return;
			
		if (state.compareTo("+1") == 0)
			tempture++;
		else if (state.compareTo("-1") == 0)
			tempture--;
		else {
			try {
				tempture = Integer.parseInt(state);
			}
			catch (Exception e) {};
		}
		
		tempVar.setValue(Integer.toString(tempture));
	}
	
	public String getTempture()
	{
		return Integer.toString(tempture);
	}
	
	private int securegroupsupport = 1;
	
	public void setSecureGroupSupport(boolean b)
	{
		secureGroupVar.setValue(Integer.toString(securegroupsupport));
	}
	
	public String isSupportSecureGroup()
	{
		return Integer.toString(securegroupsupport);
	}

	////////////////////////////////////////////////
	// ActionListener
	////////////////////////////////////////////////

	public boolean actionControlReceived(Action action)
	{
		String actionName = action.getName();
		if (actionName.equals("GetPower") == true) {
			String state = getPowerState();
			Argument powerArg = action.getArgument("Power");
			powerArg.setValue(state);
			comp.repaint();
			return true;
		}
		if (actionName.equals("SetPower") == true) {
			Argument powerArg = action.getArgument("Power");
			String state = powerArg.getValue();
			setPowerState(state);
			state = getPowerState();
			Argument resultArg = action.getArgument("Result");
			resultArg.setValue(state);
			comp.repaint();
			return true;
		}
		if (actionName.equals("GetTemp") == true) {
			String temp = getTempture();
			Argument tempArg = action.getArgument("Temp");
			tempArg.setValue(temp);
			comp.repaint();
			return true;
		}
		if (actionName.equals("SetTemp") == true) {
			Argument powerArg = action.getArgument("Temp");
			String temp = powerArg.getValue();
			setTempture(temp);
			temp = getTempture();
			Argument resultArg = action.getArgument("Result");
			resultArg.setValue(temp);
			comp.repaint();
			return true;
		}
		if (actionName.equals("IsSupportSecureGroup") == true) {
			String securegroupsupport = isSupportSecureGroup();
			Argument secureGroupArg = action.getArgument("SecureGroupSupport");
			secureGroupArg.setValue(securegroupsupport);
			return true;
		}
		if(actionName.equals("IsSecured")== true){
			Argument isSecuredArg = action.getArgument("Result");
			isSecuredArg.setValue("1");
			return true;
		}
		if(actionName.equals("GetKeyInformation") == true){
			Argument keyInformationArg = action.getArgument("KeyInformation");
			String requestMessage = action.getArgumentValue("RequestMessage");
			String xxxMessage = action.getArgumentValue("XXXMessage");
			System.out.println("Request message="+requestMessage);
			System.out.println("XXXMessage="+xxxMessage);
			action.setStatus(606);
			//String keyInformationStr = keyInformation.getValue();
			//keyInformationArg.setValue(keyInformationStr);
			return false;
		}
		return false;
	}

	////////////////////////////////////////////////
	// QueryListener
	////////////////////////////////////////////////

	public boolean queryControlReceived(StateVariable stateVar)
	{
		stateVar.setValue(getPowerState());
		return true;
	}

	////////////////////////////////////////////////
	// update
	////////////////////////////////////////////////

	public void update()
	{
	}
	
	
	//Unicast response port 4555
    public void performFooResponse(FooPacket pkg){
    	//System.out.println("Perform register");  	
    	byte[] databytes = pkg.getData();
    	
    	String dataStr = new String(databytes, 0, databytes.length);
    	String[] dataArr = dataStr.split(":");
    	String head =  dataArr[0];
    	String[] headArr = head.split(" ");
    	
    	//determine the request message
    	if(headArr[0].equals("REG")){
    		if(headArr[1].equals("REQUEST"))
    			this.onRegisterRequest(pkg);
    		if(headArr[1].equals("CONFIRM"))
    			this.onRegisterConfirm(pkg);
    	}
    	
    }
    
    public void performRekey(FooPacket pkg){
    	byte[] databytes = pkg.getData();
    	
    	String dataStr = new String(databytes, 0, databytes.length);
    	String[] dataArr = dataStr.split(":");
    	String head =  dataArr[0];
    	String[] headArr = head.split(" ");
    	//System.out.println("Perform rekey");
    	
    	if(head.equals("REKEY CONFIRM")){
    		this.onRekeyConfirm(pkg);
    	}
    }
	
	
	// Secure Group Communication
	
	//Register
	//1. Control Point send    REG REQUEST:::#SEQ,,, ID
	//2. ---Device send        REG REPLY::: #SEQ,,, PubID(dev),,, RND,,, ID
	//3. Control Point 
	//   3.1 Compute secret key = PubID(dev) x CP's secret
	//   3.2 Send              REG CONFIRM:::#SEQ,,, PubID(cp),,, H(RND, secret),,, ID
	//4. ---Device
	//   4.1 Compute secret key = PubID(cp) x DEV's secret
	//   4.2 Check H(RND, secret) <-- authentication
	//   4.3 Update new group key
	//   4.4 Update to exist member (optional)
	//   4.5 bit wise xor group key with secret key
	//   4.6 send 			   REG DONE:::#SEQ,,, xor result,,, ID, groupkeyID 
	//5. Control Point
	//   5.1 Compute group key = xor result xor secret key
	//   5.2 Update group key 
	
    private void onRegisterRequest(FooPacket pkg){
    	
    	
    	//1. Extract information
    	byte[] dataBytes = pkg.getData();
    	String dataStr = new String(dataBytes,0,dataBytes.length);
    	String[] dataArr = dataStr.split(":");
    	//String[] headArr = dataArr[0].split(" "); //REQUEST OR COMFIRM
    	String[] bodyArr = dataArr[1].split(","); //DATA-VALUE PAIR
    	
    	String seq = bodyArr[0];
    	String id = bodyArr[1];
    	
    	String ipAddr = pkg.getDatagramPacket().getAddress().getHostAddress();
    	int remotePort = pkg.getDatagramPacket().getPort();
    	
    	System.out.println("REG REQUEST received id="+id);
    	//Show request details
    	/*
    	System.out.println("SEQ# "+seq);
    	System.out.println("ID "+id);
    	System.out.println("RemotePort "+remotePort+"\n\n");
    	*/
    	//if(this.nodeSizeStart == 0){
    	//	this.nodeSizeStart = getMemoryUse();
    	//}
    	
    	//Check node in list	
    	SecureNode node  = this.secureNodeList.getSecureNode(id);
    	if(node != null){
    		//Check informations
    		//1. id 2.seq
    		String oldSeq = node.getSeq();
    		if(!oldSeq.equals(seq)){
    			System.err.println("[REJECT]REGISTERED NODE");
    			return;
    		}
    		
    		//else retransmit
    		
    		
    	}else{ //completely new

        	node = new SecureNode(id);
        	node.setSeq(seq);
        	this.secureNodeList.add(node);
    	}
    	
    	  	
    	
    	//2. ---Device send        REG REPLY::: #SEQ,,, PubID(dev),,, RND,,, ID
    	//2.1 read key file
    	//readKeyFile();
    	
    	//make 2 dimension arrays into one long array
    	byte[] tmpPubID = new byte[(l+1)*keylen];
    	int current = 0;
    	for(int i=0; i<l+1; i++){
    		System.arraycopy(publicIDBytes[i], 0, tmpPubID, current, keylen);
    		current += keylen;
    	}
    	String pubIDStr = AirconDevice.toHexString(tmpPubID);
    	
    	
    	
    	//2.2 generate random number
    	Random rand = new Random();
    	int RND = rand.nextInt();
    	String RNDStr = String.valueOf(RND);
    	
    	//System.out.println("Random number "+RND);
    	node.setRand(RNDStr);
    	
    	
    	//2.3 prepare reply string
    	String responseStr = "REG REPLY:";
    	responseStr += seq+",";            
    	responseStr += pubIDStr+",";
    	responseStr += RND+",";
    	responseStr += id;
    	//System.out.println(responseStr);
    	//System.out.println("REG REPLY ID="+id);
    	
    	sendResponse(responseStr, ipAddr, remotePort);
    	
    	
    	//2.4 networking
    	//try{
    	//	Thread.sleep(500);
    	//}catch(InterruptedException e){}
    	/*
    	FooResponse replyResponse = new FooResponse();
    	replyResponse.setData(responseStr.getBytes());
    	FooUSocket uSocket = new FooUSocket();
    	uSocket.open();
    	uSocket.post(ipAddr, remotePort, replyResponse);
    	uSocket.close();
    	*/
    	
    	
    	//let's turn to control point
    	
    }
    
    private void onRegisterConfirm(FooPacket pkg){
    	
	   	
    	//Extract information
    	byte[] databytes = pkg.getData();
    	String dataStr = new String(databytes, 0, databytes.length);
    	String[] dataArr = dataStr.split(":");
    	String body =  dataArr[1];
    	String[] bodyArr = body.split(",");
    	
    	String ipAddr = pkg.getDatagramPacket().getAddress().getHostAddress();
    	int remotePort = pkg.getDatagramPacket().getPort();
    	
    	String seq = bodyArr[0];
    	String pubIDStr = bodyArr[1];
    	String messageDigestStr = bodyArr[2];
    	String id = bodyArr[3];
    	String port = bodyArr[4];
    	
    	System.out.println("REG CONFIRM received id="+id);
    	
    	
    	
    	//parse to byte array
    	byte[] pubIDBytes = AirconDevice.hexStringToBytes(pubIDStr);
    	
		
    	//Show information
    	/*
    	System.out.println("SEQ# "+seq);
    	System.out.println("ID "+id);
    	//System.out.println("PubID "+pubIDStr);
    	System.out.println("Message Digested "+messageDigestStr);
    	System.out.println("Group port "+port);
    	*/
    	SecureNode node = this.secureNodeList.getSecureNode(id);
    	
    	if(node.isRegistered()){
    		// resend reg don do not rekey
    		System.out.println("REG CONFIRM on registered node");
    		String lastMessage = node.getLastMessage();
    		
    		// Network
        	FooResponse replyResponse = new FooResponse();
        	replyResponse.setData(lastMessage.getBytes());
        	FooUSocket uSocket = new FooUSocket();
        	uSocket.open();
        	uSocket.post(ipAddr, remotePort, replyResponse);
        	uSocket.close();
        	
        	return;
    	}
    	
    	else if(node.isWaitconfirm()){//if node is waiting in confirm queue
    		System.out.println("REG CONFIRM wait time out");
    		refreshKey();
    		return;
    	}
    	
    	//String seq2 = node.getSeq();
    	//String id2 = node.getName();
    	//System.out.println("seq2="+seq2);
    	//System.out.println("nodeid2="+id2);
    	
    	
    	
    	if(node == null || !node.getSeq().equals(seq)){
    		System.err.println("[REGISTER ERROR] wrong sequnce");
    		return;
    	}
    	
    	//4.1 Compute secret key
    	byte[] secret = computeSecret(pubIDBytes);
    	
    	
    	
    	//String secretStr = new String(secret, 0, secret.length);
    	//System.out.println("Secret "+secretStr);
    	
    	//   4.2 Check H(RND, secret) <-- authentication
    	String RND = node.getRand();
    	byte[] RNDbytes = RND.getBytes();
    	int RNDbyteslen = RNDbytes.length;
    	byte[] tmpBytesToHash = new byte[RNDbyteslen+secret.length];
    	System.arraycopy(RNDbytes, 0, tmpBytesToHash, 0, RNDbyteslen);
    	System.arraycopy(secret, 0, tmpBytesToHash, RNDbyteslen, secret.length);
    	
    	byte[] messageDigestOri = null;
    	try{
    		MessageDigest algorithm = MessageDigest.getInstance("MD5");
    		algorithm.reset();
    		algorithm.update(tmpBytesToHash);
    		messageDigestOri = algorithm.digest();
    	}catch(NoSuchAlgorithmException e){}
    	String messageDigestOriStr = AirconDevice.toHexString(messageDigestOri);
    	
    	
    	
    	
    	
    	//authentication++++++++++++++++++++++++++++++++++++++
    	if(!messageDigestOriStr.equals(messageDigestStr)){
    		System.err.println("REG FAILED");
    		return;
    	}
    	
    	// port use to update group key
    	node.setRequestPort(Integer.parseInt(port));
    	node.setIpAddr(ipAddr);
    	node.setSecret(secret);  // pass by reference (node's secret value in nodelist will change)
    	
    	
    	
    	//rekey if need
    	int groupsize = this.secureNodeList.getRegisteredNode();
    	if(groupsize > 0){
    		System.out.println("[confirmlist]rekey required id="+node.getName());
    		
    		long startTime = System.currentTimeMillis();
    		node.setWaitTime(startTime);
    		node.setConfirmPkg(pkg);
    		
    		//wait to rekey
        	synchronized (confirmList) {

    			int size = confirmList.size();
    			node.setWaitconfirm(true);
				confirmList.add(node);
				System.out.println("[confirmlist]Added id="+node.getName());
				
    			if(size == 0){
    				joinRekey(id);
    			}
	
    		}
        	
    		
    		
    	}else{// first node
    		rekey();
    		onRegisterDone(node, ipAddr, remotePort);
    	}

    }
    
    private void onRegisterDone(SecureNode node, String ipAddr, int port){
    	
    	//System.out.println("******REG SUCCESS ID="+id+"******");
    	//   4.3 Update new group key
    	//   4.4 Update to exist member (optional)
    	//   4.5 bit wise xor group key with secret key
    	//   4.6 send 			   REG DONE:#SEQ, xor result, ID
    	String seq = node.getSeq();
    	String id = node.getName();
    	byte[] secret = node.getSecret();
    	
    	
    	String responseStr = "REG DONE:";
    	responseStr += seq+",";
    	
    	
    	
    	
    	node.setgroupkeyID(getGroupkeyID());
    	node.setRegistered(true);
    	
    	byte[] groupkey = getGroupkey();
    	
    	byte[] xorResult = AirconDevice.bitwiseXOR(secret, groupkey);
    	String xorStr = AirconDevice.toHexString(xorResult);
    	
    	responseStr += xorStr+",";
    	responseStr += id+",";
    	responseStr += getGroupkeyID();
    	
    	node.setLastMessage(responseStr);
    	
    	
    	// Network
    	FooResponse replyResponse = new FooResponse();
    	replyResponse.setData(responseStr.getBytes());
    	FooUSocket uSocket = new FooUSocket();
    	uSocket.open();
    	uSocket.post(ipAddr, port, replyResponse);
    	uSocket.close();
    	
    	
		
		try{
			Thread.sleep(1000);
		}catch(Exception e){}
		/*
    	this.testSecureGroup(node.getIpAddr());
    	long nodeSizefinish = getMemoryUse();
    	long nodeSizeChange = nodeSizefinish - nodeSizeLast;
    	this.nodeSizeLast = nodeSizefinish;
    	long totalMemoryChanged = nodeSizefinish - nodeSizeStart;
    	*/
    	//System.out.print(/*"[MEM_STAT]Memory changed "+*/nodeSizeChange+" ");
    	//System.out.println(/*"[MEM_STAT]Total memory used "+*/totalMemoryChanged);
    	
    	System.out.println("*** *** *** register success id="+node.getName());
    	int registNum = this.secureNodeList.getRegisteredNode();
    	int poolsize = this.secureNodeList.size();
    	
    	System.out.println("*** *** *** pool size="+poolsize+" registered="+registNum);

    }
    
    private void onRekeyConfirm(FooPacket pkg){
		// 1. after broadcasted update
		// 2. on notify check time out
		// 3. if time out check node list
    	
    	String ipAddr = pkg.getDatagramPacket().getAddress().getHostAddress();
    	int port = pkg.getDatagramPacket().getPort();
    	
    	byte[] dataBytes = pkg.getData();
		String dataStr = new String(dataBytes, 0, dataBytes.length);
		String[] dataArr = dataStr.split(":");
		String body = dataArr[1];
		String[] bodyArr = body.split(",");
		
		String seq = bodyArr[0];
		String id = bodyArr[1];
		String groupkeyID = bodyArr[2];
		int nodeGroupkeyID = Integer.parseInt(groupkeyID);
		
		//update in securenodelist
		SecureNode node = this.secureNodeList.getSecureNode(id);
		node.setgroupkeyID(groupkeyID);
		
		System.out.println("[REKEY] Successed id="+id+" groupkeyid="+groupkeyID);
		
		
		
		
		//acknowledge to client
		String requestStr = "REKEY DONE:";
		requestStr += seq+",";
		requestStr += id;
		
		sendResponse(requestStr, ipAddr, port);
		
		refreshKey();
		
		/*
		if(Integer.parseInt(getGroupkeyID())>nodeGroupkeyID){
			Random rand = new Random();
			int t = rand.nextInt(2000);
			try{
				Thread.sleep(t);
			}catch(Exception e){}
			
			FooRequest req = getLastRekey();
			groupBroadcast(req);
		}
		*/
		

		

    }
    
    private void refreshKey(){
		//check rekey status
		//check group key update status in node list
		if(secureNodeList.rekey(this.getGroupkeyID()) != null){
			System.out.println("[REKEY] Member remained");
			//rekey does not done
			//check time out then broadcast again
			synchronized (confirmList) {
				SecureNode waitnode = confirmList.get(0);
				long currentTime = System.currentTimeMillis();
				long espTime = currentTime - waitnode.getWaitTime();
				
				
				
				if(espTime > nodeConfirmTimeout){
					//rekey again
					System.out.println("[REKEY] Time out");
					
					//set new time
					waitnode.setWaitTime(currentTime);
					
					FooRequest req = getLastRekey();
					groupBroadcast(req);
					
					//System.out.println("esp time="+espTime);
				}else{
					//continue waiting;
					System.out.println("[REKEY] waiting");
				}
			}
		}else{// rekey success
			//confirm 
			
			
			synchronized (confirmList) {
				
				
				
				//if(size == 0){
				//	return;
				//}
				
				//REG DONE
				SecureNode waitnode = confirmList.get(0);
				System.out.println("[REKEY-fin] send REG DONE to "+waitnode.getName());
				
				String waitNodeip = waitnode.getIpAddr();
				FooPacket dpkg = waitnode.getConfirmPkg();
				DatagramPacket dgram = dpkg.getDatagramPacket();
				int waitNodeport = dgram.getPort();
				
				onRegisterDone(waitnode, waitNodeip, waitNodeport);
				
				//remove node from confirm queue
				waitnode.setConfirmPkg(null);
				String rmID = waitnode.getName();
				System.out.println("[REKEY-fin] removing id="+rmID);
				waitnode.setWaitconfirm(false);
				confirmList.remove(0);
				
				int size = confirmList.size();
				
				System.out.println("[REKEY-fin] queue="+size);
				
				
				//check if queue in not empty rekey the next
				if(size > 0){
					System.out.println("[REKEY-fin] continue next rekey");
					joinRekey(confirmList.get(0).getName());
				}else{
					System.out.println("[REKEY-fin] finished");
				}
				
			}
		}
    }
    
	private void sendResponse(String requestStr, String ipAddr, int port){
		FooResponse resp = new FooResponse();
		resp.setData(requestStr.getBytes());
		
		FooUSocket sock = getFooUSocket();
		boolean retry = true;
		while(retry){
			retry = !sock.post(ipAddr, port, resp);
		}
		
	}
	
	
    
    public static byte[] bitwiseXOR(byte[] b1, byte[] b2){
    	if(b1.length != b2.length)
    		return null;
    	
    	byte[] tmp = new byte[b1.length];
    	
    	for(int i=0; i<tmp.length; i++){
    		tmp[i] = (byte)(b1[i] ^ b2[i]);
    	}
    	
    	return tmp;
    }
    
    private boolean readKeyFile(){
    	// see full verion at org.geed.kpd.test.TestKey
    	
		
		try{
			// Read key bytes (8)
			FileInputStream in = new FileInputStream("secret\\m"+getName()+".txt");
			
		
			this.secretBytes = new byte[l+1][keylen];
			this.publicIDBytes = new byte[l+1][keylen];
			

			
			for(int i=0; i<l+1; i++){
				in.read(this.secretBytes[i], 0, this.secretBytes[i].length);
				//in.read(publicG1[i], 0, publicG1[i].length);
				//System.out.print(this.secretBytes[i]+" ");
						
			}
			//System.out.println();
			
			for(int i=0; i<l+1; i++){
				//in.read(secretA1[i], 0, secretA1[i].length);
				in.read(this.publicIDBytes[i], 0, this.publicIDBytes[i].length);
				//System.out.print(this.publicIDBytes[i]+" ");
			}
			
			
			
			
			return true;
			
		}catch(IOException ioe){
			ioe.printStackTrace();
		}
		
		return false;
		
		
    }
    
    private byte[] computeSecret(byte[] publicIDBytes){
		// see full verion at org.geed.kpd.test.TestKey
		
		byte[][] publicID = new byte[l+1][keylen];
		int current =0;
		for(int i=0; i<l+1; i++){
			System.arraycopy(publicIDBytes, current, publicID[i], 0, keylen);
			current += keylen;
		}
		
		
		
		byte[] result = new byte[keylen];
		for(int i=0; i<l+1; i++){
			byte[] tmp = new byte[keylen];
			
			for(int j=0; j<keylen; j++){
				tmp[j] = (byte)(this.secretBytes[i][j]*publicID[i][j]);
			}
			
			for(int j=0; j<keylen; j++){
				result[j] += tmp[j];
			}
			
			
		}
		
		//set secret bytes use to generate secret key
		return result;
	}
	
    /*
     * Converts a byte to hex digit and writes to the supplied buffer
     */
    private static void byte2hex(byte b, StringBuffer buf) {
        char[] hexChars = { '0', '1', '2', '3', '4', '5', '6', '7', '8',
                            '9', 'A', 'B', 'C', 'D', 'E', 'F' };
        int high = ((b & 0xf0) >> 4);
        int low = (b & 0x0f);
        buf.append(hexChars[high]);
        buf.append(hexChars[low]);
    }

    private static byte[] hexStringToBytes( String s ) {
    	/* Thanks Mask_s http://forums.sun.com/thread.jspa?threadID=5129024 */

    	int iLength = s.length();
    	int iBuff = iLength / 2;

    	byte[] buff = new byte[ iBuff ];

    	int j = 0;
    	for(int i = 0; i < iLength; i+=2) {

	    	try {
	
	    		String s1 = s.substring(i, i+2);
	    		buff[j++] = (byte) Integer.parseInt(s1, 16);
	
	    	} catch ( Exception e ) {
	    		e.printStackTrace();
	    	}

    	}

    	return buff;

    }

    
    /*
     * Converts a byte array to hex string
     */
    private static String toHexString(byte[] block) {
        StringBuffer buf = new StringBuffer();
       
        int len = block.length;

        //int j=0;
        for (int i = 0; i < len; i++) {
            // j++;
        	 byte2hex(block[i], buf);
             //if (i < len-1) {
             //    buf.append(":");
             //}
             //if (j>=25){
            //	 buf.append("\n\t\t");
            //	 j=0;
             //}
        }
        return buf.toString();
    }

	private byte[] getGroupkey() {
		return currentGroupkey;
	}
	

	
	private FooUSocket getFooUSocket(){
		return (FooUSocket)this.unicastServer;
	}

	private void setGroupkey(byte[] groupkey) {
		this.currentGroupkey = groupkey;
	}
	
	private void rekey(){
		Random rand = new Random();
		byte[] b = new byte[keylen];
		
		rand.nextBytes(b);
		
		setGroupkey(b);
		setGroupkeyID();
		
	}
	
	private void setGroupkeyID(){
		this.groupkeyID++;
		this.currentGroupkeyID = String.valueOf(this.groupkeyID);
	}
	
	private String getGroupkeyID(){
		return this.currentGroupkeyID;
	}
	
	private void joinRekey(String newnodeID){
		// 1. broadcast update
		// 2. on notify check time out
		// 3. if time out check node list
		//REKEY NOTIFY:SEQ#, XOR(keynew, keyold)
		
		String requestStr = "REKEY NOTIFY:";
		byte[] keyold = getGroupkey();
		rekey();
		byte[] keynew = getGroupkey();
		
		byte[] result = AirconDevice.bitwiseXOR(keyold, keynew);
		String resultStr = AirconDevice.toHexString(result);
		
		Random rand = new Random();
		String seq = String.valueOf(rand.nextInt());
		
		requestStr += seq+",";
		requestStr += resultStr+",";
		requestStr += getGroupkeyID()+",";
		requestStr += newnodeID;
		
		FooRequest req = new FooRequest();
		req.setData(requestStr.getBytes());
		
		//onetime notify
		groupBroadcast(req);
		setLastRekey(req);
		
		//check if all member in group
		//while(/* all member rekeyed */){
			
		//}
	
		
		
	}
	
	public void printInfo(){
		secureNodeList.printNodeInfo();
		
	}
	

	
	private void groupBroadcast(FooRequest req){
		FooMUSocket muSock = new FooMUSocket();
		muSock.post(req);
		
		System.out.println("[group] Data sent");
	}
	
	private void leaveRekey(SecureNode nNode){
		// unicast update
	}
	
	private void testSecureGroup(String ipaddr){
		byte encBytes[] = null;
		try{
			Cipher enc = Cipher.getInstance("DES");
			DESKeySpec keyspec = new DESKeySpec(getGroupkey());
			SecretKeyFactory keyfact = SecretKeyFactory.getInstance("DES");
			SecretKey secret = keyfact.generateSecret(keyspec);
			enc.init(Cipher.ENCRYPT_MODE, secret);
			
			encBytes = enc.doFinal("HELLo WoRLD".getBytes());
		}catch(Exception e){
			e.printStackTrace();
		}
		
		FooUSocket uSock = new FooUSocket();
		uSock.open();
		
		FooResponse resp = new FooResponse();
		resp.setData(encBytes);
		uSock.post(ipaddr, 4566, resp);
		uSock.close();
	}
	
	  // PRIVATE //
	  /*
	   * Thanks to http://www.javapractices.com/topic/TopicAction.do?Id=83
	   */
	  private static int fSAMPLE_SIZE = 100;
	  private static long fSLEEP_INTERVAL = 100;

	  private static long getMemoryUse(){
	    putOutTheGarbage();
	    long totalMemory = Runtime.getRuntime().totalMemory();

	    putOutTheGarbage();
	    long freeMemory = Runtime.getRuntime().freeMemory();

	    return (totalMemory - freeMemory);
	  }

	  private static void putOutTheGarbage() {
	    collectGarbage();
	    collectGarbage();
	  }

	  private static void collectGarbage() {
	    try {
	      System.gc();
	      Thread.currentThread().sleep(fSLEEP_INTERVAL);
	      System.runFinalization();
	      Thread.currentThread().sleep(fSLEEP_INTERVAL);
	    }
	    catch (InterruptedException ex){
	      ex.printStackTrace();
	    }
	  }

	private FooRequest getLastRekey() {
		return lastRekey;
	}

	private void setLastRekey(FooRequest lastRekey) {
		this.lastRekey = lastRekey;
	}
}

